package com.hzit.config;

import org.apache.logging.log4j.message.ReusableMessage;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;

import javax.sql.DataSource;

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true,securedEnabled = true)
public class MySpringSecurityConfig{
	@Autowired
	private UserDetailsService userDetailsService;
	@Autowired
	private AccessDeniedHandler accessDeniedHandler;
	// @Autowired
	// private AuthenticationEntryPoint authenticationEntryPoint;
	@Autowired
	private AuthenticationFailureHandler authenticationFailureHandler;
	@Autowired
	private DataSource dataSource;
	@Bean
	public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception{
		HttpSecurity security =
			http.formLogin()
				.loginPage("/login.html")                // 自定义登录页面的地址
				.loginProcessingUrl("/login")            // 定义的登录处理页面，由系统自动生成，我们不用管它（放行也由系统完成）
				//.failureUrl("/fail.html")              // 自定义登录页面的认证失败处理页面（方法一：重定向实现）
				.failureForwardUrl("/user/fail")         // 自定义登录页面的认证失败处理器（方法二：转发实现，此时控制器方法必须是post方法）
				//.failureHandler(authenticationFailureHandler)  // 自定义登录页面的认证失败处理器（方法三:可以定制，更丰富）
				//.usernameParameter("uname")            // 用户名在表单中的名字，默认为username
				//.passwordParameter("pwd")              // 密码在表单中的名字，默认为password
				// .successForwardUrl("/user/welcome")   // 注：这里需要指定一个控制器映射路径名，而不能使用静态资源名字（需要使用模板技术）
				.defaultSuccessUrl("/welcome.html")      // 上次请求的地址可能就是/login.html，而不是其它页面，如果是其它页面成功后的跳转需要使用successForwardUrl（）
				.and()
				.authorizeRequests().antMatchers("/login.html","/welcome.html","/fail.html","/user/fail").permitAll() //放行相关的页面
				// .antMatchers("/student/add").hasAuthority("admin")                        // 代表有admin权限才能访问相关资源
				// .antMatchers("/student/update").hasAnyAuthority("admin123","user","test") // 代表有三者中的任意一个权限就可以访问资源/student/update
				// .antMatchers("/student/delete").hasRole("haha")                           // 代表有ROLE_haha这个角色才能访问资源/student/delete,注意这里的角色会自动加上ROLE_
				// .antMatchers("/student/list").hasAnyRole("ha","hello","test","user")      // 代表只要有四个角色中的其中之一就可以访问资源/student/list了
				.anyRequest().authenticated()               // 其它的任何请求，都需要认证后才能访问
				.and()
				.csrf().disable()                           // 禁用csrf跨域请求伪造功能，保障提交数据的安全性
				.userDetailsService(userDetailsService);    // 设置要进行认证的userDetailService对象


		// 认证与授权失败的处理
		security.exceptionHandling()
				.accessDeniedHandler(accessDeniedHandler);                // 处理授权403异常 (授权处理方式一：使用处理器方式实现)！
				// .authenticationEntryPoint(authenticationEntryPoint);   // 处理认证失败后的异常,重写了UsernamePasswordAuthenticationFilter类会起作用(异步认证使用)，否则，一上来就加载它
				//.accessDeniedPage("/accessDeined.html");                // 处理授权403异常 (授权处理方式一：使用页面的方式实现)

		// 定义rememberMe，此功能与浏览器相关，同一浏览器可以任意访问，但是不同浏览器不行，因为浏览器对于cookie的加密方式不一样。
		security.rememberMe()
				.rememberMeCookieName("remember-cookie")
				// .rememberMeParameter("remember")          // 指定remember在表单中的名字
				.tokenRepository(persistentTokenRepository())
				.tokenValiditySeconds(604800) ;              // 设置7天到期时间，以s为单位
		return security.build();
	}

	// 配置加密器，用于加密用户在表单中输入的密码，加密完成后再与数据库中的密码比较
	@Bean
	public PasswordEncoder passwordEncoder(){
		return new BCryptPasswordEncoder();
	}

	// 配置remember-me的持久化的存储对象
	@Bean
	public PersistentTokenRepository persistentTokenRepository(){
		JdbcTokenRepositoryImpl persistentTokenRepository = new JdbcTokenRepositoryImpl();
		persistentTokenRepository.setDataSource(dataSource);                // 设置关联的数据源
		persistentTokenRepository.setCreateTableOnStartup(true);            // 启动时创建表,第一次使用设置为true,以后修改为false
		return persistentTokenRepository;
	}
}
